package com.sjn.auth.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

/**
 * @Author: sjn
 * @Data: 2025/6/18 16:00
 */

@Configuration
public class OAuth2AuthorizationServerConfig {

    /**
     * 该方法配置了OAuth2授权服务器的安全过滤链，主要功能如下：
     * 应用OAuth2授权服务器的默认安全配置；
     * 启用OpenID Connect 1.0支持；
     * 配置异常处理，设置未认证用户的入口为/login；
     * 启用JWT格式的OAuth2资源服务器支持。
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // Enable OpenID Connect 1.0
                .oidc(Customizer.withDefaults());

        http
                .exceptionHandling(exceptions ->
                        exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> {
                        })
                );

        return http.build();
    }

    /**
     * 该方法定义了一个名为 registeredClientRepository 的 Bean，用于配置一个内存中的 OAuth2 客户端信息仓库。它创建了一个客户端对象，具备以下特征：
     * 客户端 ID 为 client，密钥为经 BCrypt 编码的 secret
     * 使用 CLIENT_SECRET_BASIC 认证方式
     * 支持授权码、刷新令牌和客户端凭证三种授权类型
     * 设置了两个重定向 URI
     * 包含 OpenID、Profile、message.read 和 message.write 四种权限范围
     * 要求用户授权确认（consent）
     * 最终将该客户端注册到内存仓库 InMemoryRegisteredClientRepository 并返回。
     *
     * @return
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("client")
                .clientSecret(passwordEncoder().encode("secret"))
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/demo-client")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("message.read")
                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    /**
     * 该方法创建了一个用于OAuth 2.0授权服务器的 JWKSource<SecurityContext> Bean，其作用是提供一组加密密钥（JWK），供安全协议使用。
     * 具体逻辑如下：
     * 生成RSA密钥对：调用 generateRsaKey() 方法生成2048位的RSA公私钥对。
     * 构建RSAKey对象：使用公钥和私钥构造一个带有唯一keyID的 RSAKey 对象。
     * 创建JWKSet：将 RSAKey 封装为一个包含单个密钥的 JWKSet。
     * 返回ImmutableJWKSet：将 JWKSet 包装成不可变的 ImmutableJWKSet 并返回，确保密钥集合在运行时不会被修改。
     *
     * @return
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 该方法创建了一个用于OAuth 2.0授权服务器的 JWKSource<SecurityContext> Bean，其作用是提供一组加密密钥（JWK），供安全协议使用。
     * 具体逻辑如下：
     * 生成RSA密钥对：调用 generateRsaKey() 方法生成2048位的RSA公私钥对。
     * 构建RSAKey对象：使用公钥和私钥构造一个带有唯一keyID的 RSAKey 对象。
     * 创建JWKSet：将 RSAKey 封装为一个包含单个密钥的 JWKSet。
     * 返回ImmutableJWKSet：将 JWKSet 包装成不可变的 ImmutableJWKSet 并返回，确保密钥集合在运行时不会被修改。
     *
     * @return
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * 该方法创建了一个用于OAuth 2.0授权服务器的 JWKSource<SecurityContext> Bean，其作用是提供一组加密密钥（JWK），供安全协议使用。
     * 具体逻辑如下：
     * 生成RSA密钥对：调用 generateRsaKey() 方法生成2048位的RSA公私钥对。
     * 构建RSAKey对象：使用公钥和私钥构造一个带有唯一keyID的 RSAKey 对象。
     * 创建JWKSet：将 RSAKey 封装为一个包含单个密钥的 JWKSet。
     * 返回ImmutableJWKSet：将 JWKSet 包装成不可变的 ImmutableJWKSet 并返回，确保密钥集合在运行时不会被修改。
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 该方法创建并返回一个 AuthorizationServerSettings 对象，用于配置OAuth2授权服务器的基本设置。其中：
     * 使用 @Bean 注解将该对象注册为Spring容器中的一个Bean；
     * 设置授权服务器的签发者（issuer）为 http://auth-server:9000，表示该服务器生成的令牌将以此URL作为标识。
     *
     * @return
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://auth-server:9000")
                .build();
    }
}
